#!/usr/bin/perl
# CCN::IPC::UnspoolRemote.pm
# Copyright (C) 2006, 2007  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information: http://carrierclass.net/
#
use strict; use warnings;

# A Jabber client that sends remote spool commands.

package CCN::IPC::UnspoolRemote;
use base qw(CCN::Class);

use CCN::Daemon;
use CCN::IPC::SingleInstance;


sub initialize
{
    my $self = shift;
    
    $self->{daemon} = new CCN::Daemon 'SpoolRemote';
    $self->{single} = new CCN::IPC::SingleInstance($self->here(),$self->cmd());
}


sub run()
{
    our $self = shift;

    # It's still running if it was last seen no less than one minutes ago.
    $self->log_for('info','UnspoolRemote already running.'), exit 0
        if defined $self->{single}->last_touched and $self->{single}->last_touched > time() - 90;

    $self->log_for('info','Killing late UnspoolRemote.'), $self->{single}->killit(),
        if defined $self->{single}->last_touched and $self->{single}->last_touched > time() - 180;

    # /Preparatory code

    our $jabber_config  = $self->jabber_config();

    our $Connection;

    # Dedicated processing
    sub process()
    {
        my $info = $self->spool->message_store->get_next_remote($self->spool->get_local_pk());
        return 0 if not defined $info;

        my $remote_pk = $info->{target};
        my $method = 'spool';
        my @params = map {$info->{$_}} qw(pk source target content priority due);
        # This might work in domain-based ejabberd config, but it doesn't in single.
        # my $recipient = 's'.$remote_pk.'@'.$domain.'/RPCServer';
        # Let's use this for now.
        my $recipient = 's'.$remote_pk.'@'.$jabber_config->{server}.'/RPCServer';

        my $doing_this = "RPCCall(to:$recipient,method:$method)";
        # $d->log_for ('debug', $doing_this);

        my @result = $Connection->RPCCall(
            to => $recipient,
            methodname => $method,
            params => [@params],
            mode => 'block',
            timeout => 3,
        );

        if( defined $result[0] and $result[0] eq 'ok' )
        {
            # RPCCall erroneously return OK with the <params/> stanza as @result even when
            # (for example) the jabber server returns an error (such as a 404).
            $self->log_for ('info', "$doing_this completed OK with return value ".join(',',@{$result[1]})),
            $self->spool->message_store->clear($info->{pk}),
            return 0
                if defined $result[1] and defined $result[1]->[0] and $result[1]->[0] eq 'ccn-received'
                and defined $result[1]->[1];
            
            # Should notify the parent (e.g. inside a CCN::IPC::Ticket)
        }

        if( defined $result[0] and $result[0] eq 'fault' )
        {
            $self->log_for ('warn', "$doing_this failed:".$result[1]->{faultString})
                if defined $result[1] and exists $result[1]->{faultString};
        }

        $self->spool->message_store->reschedule($info->{pk});

        return 1;
    }

    # Jabber processing
    my $resource = 'RPCClient';

    $self->log_for ('info', "Starting hostname:$jabber_config->{server},port:$jabber_config->{port},username:$jabber_config->{username},resource:$resource");

    sub Stop
    {
        $Connection->Disconnect();
        $self->log_for ('info', 'Disconnected.');
        return 0;
    }

    sub OnAuth()
    {
        $Connection->RosterGet();
        $Connection->PresenceSend();
        $self->log_for ('info', 'Ready');
    }
    
    our $processed_once = 0;

    sub OnProcess()
    {
        $processed_once = 1;

        if( not defined $self->{single}->last_touched or $self->{single}->last_touched <= time() - 10 )
        {
            $self->{single}->touch() or $self->log_for('err',"Could not update touch file: $!");
        }
        
        return process();
    }

    sub OnDisconnect()
    {
        # $d->log_for ('info', 'Disconnect.');
    }

    sub OnExit()
    {
        # $d->log_for ('info', 'Exit.');
    }

    sub restart()
    {
        $self->{single}->restart;
    }

    sub stop()
    {
        Stop();
        $self->{single}->stop;
    }


    # $SIG{HUP}  = \&restart;

    # On Linux, have to use the method described in perlipc:
    use POSIX;
    my $sigset = POSIX::SigSet->new();
    my $action = POSIX::SigAction->new(\&restart,
                                       $sigset,
                                       &POSIX::SA_NODEFER);
    POSIX::sigaction(&POSIX::SIGHUP, $action);

    $SIG{TERM} = \&stop;
    $SIG{INT}  = \&Stop;

    $self->{single}->touch();
    $self->log_for ('info', 'Started.');
    
    while(1)
    {
        use Net::Jabber qw(Client);
        
        $Connection = new Net::Jabber::Client(
            'debugfile'         => $self->debug_file,
            'debuglevel'        => 1,
        );

        if($Connection)
        {
            $Connection->Info( name => 'CCN RPCClient', version => '1.0' );
            
            # $Connection->callbackInit();

            $Connection->SetCallBacks(
                'onauth'        => \&OnAuth,
                'onprocess'     => \&OnProcess,
                'ondisconnect'  => \&OnDisconnect,
                'onexit'        => \&OnExit,
            );

            $processed_once = 0;

            $Connection->Execute(
                'myhostname'        =>  $jabber_config->{domain},
                'hostname'          =>  $jabber_config->{server},
                'port'              =>  $jabber_config->{port},
                'username'          =>  $jabber_config->{username},
                'password'          =>  $jabber_config->{password},
                'resource'          =>  $resource,
                'register'          =>  0, # Attempt to register if account doesn't exist
                'processtimeout'    =>  1, # Process every one second
                'connectattempts'   =>  2, # -1 = Forever
            );
            
        }

        undef $Connection;

        # if the connection simply failed (e.g. wrong password), do not retry.
        $self->log_for('err', 'UnspoolRemote connection failed'),
        $self->{single}->stop(2)
            if not $processed_once;

        sleep(1);
    }
}
1;
